package com.vast.admin.utils;

import com.vast.admin.entity.Placeholder;
import com.vast.common.constant.MessageCode;
import com.vast.common.core.domain.Result;
import com.vast.system.exception.BusinessException;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.function.Function;
import java.util.stream.Stream;

/**
 * 占位符解析器
 */
@Component
public class PlaceholderResolver {

    public static final String CONTRACT_NO_FIELD = "系统合同编号";

    public static final String REPAYMENT_PLAN_FIELD = "#还款计划表";

    /**
     * 占位符名称长度限制
     */
    public static final Integer PLACEHOLDER_NAME_LENGTH = 20;

    /**
     * 占位符前缀
     */
    private static final String PLACEHOLDER_PREFIX = "{{";

    /**
     * 占位符后缀
     */
    private static final String PLACEHOLDER_SUFFIX = "}}";

    /**
     * 获取所有占位符
     * @param content
     * @return
     */
    public List<Placeholder> getPlaceholder(String content) {
        List<Placeholder> list = new ArrayList<>();
        int start = content.indexOf(PLACEHOLDER_PREFIX);
        int index = 1;
        boolean isContainsContractNo = false;
        //值索引
        while (start != -1) {
            int end = content.indexOf(PLACEHOLDER_SUFFIX, start);
            if (end == -1) {
                throw new BusinessException("合同模板属性【" + content.substring(start) + "】无结束标记");
            }
            String placeholderName = content.substring(start + PLACEHOLDER_PREFIX.length(), end);

            int i = placeholderName.indexOf("}");
            if (i > -1) {
                placeholderName = placeholderName.substring(0, i + 1);
                throw new BusinessException("合同模板属性【" + placeholderName + "】结束标记错误，只包含一个“}”");
            }

            if (placeholderName.length() > PLACEHOLDER_NAME_LENGTH) {
                throw new BusinessException("合同模板属性【" + placeholderName + "】超长");
            }

            if (placeholderName.indexOf(" ") > -1) {
                throw new BusinessException("合同模板属性【" + placeholderName + "】中包含空格");
            }

            char illegalChar = validateLegalString(placeholderName);
            if (illegalChar != 't') {
                throw new BusinessException("合同模板属性【" + placeholderName + "】包含非法字符【" + illegalChar + "】");
            }

            Placeholder placeholder = new Placeholder();
            String placeholderKey = "phk_c" + index;
            //将隐藏字段tag设置为-1
            if (CONTRACT_NO_FIELD.equals(placeholderName)) {
                placeholder.setRequire(-1);
                placeholder.setPlaceholderName(placeholderName);
                isContainsContractNo = true;
            } else if (REPAYMENT_PLAN_FIELD.equals(placeholderName)) {
                placeholder.setRequire(-1);
                placeholder.setPlaceholderName(placeholderName);
            } else if (placeholderName.contains(".")) {
                placeholder.setPlaceholderBelong(placeholderName.substring(0, placeholderName.indexOf(".")));
                placeholder.setPlaceholderName(placeholderName.substring(placeholderName.indexOf(".") + 1));
            } else {
                placeholder.setPlaceholderName(placeholderName);
            }
            if (!isContains(list, placeholder)) {
                placeholder.setPlaceholderKey(placeholderKey);
                list.add(placeholder);
                index ++;
            }
            start = content.indexOf(PLACEHOLDER_PREFIX, end);
        }
        if (!isContainsContractNo) {
            throw new BusinessException(new Result(MessageCode.ERROR, "合同模板中未找到" + CONTRACT_NO_FIELD));
        }
        return list;
    }

    /**
     * 是否包含
     * @param placeholders
     * @param placeholder
     * @return
     */
    private boolean isContains(List<Placeholder> placeholders, Placeholder placeholder) {
        for (Placeholder tmp : placeholders) {
            if(placeholder.getPlaceholderName() != null){
                if (tmp.getPlaceholderName().equals(placeholder.getPlaceholderName())
                        && ((tmp.getPlaceholderBelong() == null && placeholder.getPlaceholderBelong() == null)|| (tmp.getPlaceholderBelong() != null && placeholder.getPlaceholderBelong() != null && tmp.getPlaceholderBelong().equals(placeholder.getPlaceholderBelong())))) {
                    return true;
                }
            }

        }
        return false;
    }
    /**
     * 解析带有指定占位符的模板字符串，默认占位符为前缀：${  后缀：}<br/><br/>
     * 如：template = category:${}:product:${}<br/>
     * values = {"1", "2"}<br/>
     * 返回 category:1:product:2<br/>
     *
     * @param content 要解析的带有占位符的模板字符串
     * @param values   按照模板占位符索引位置设置对应的值
     * @return
     */
    public String resolve(String content, String... values) {
        int start = content.indexOf(PLACEHOLDER_PREFIX);
        if (start == -1) {
            return content;
        }
        //值索引
        int valueIndex = 0;
        StringBuilder result = new StringBuilder(content);
        while (start != -1) {
            int end = result.indexOf(PLACEHOLDER_SUFFIX);
            String replaceContent = values[valueIndex++];
            result.replace(start, end + PLACEHOLDER_SUFFIX.length(), replaceContent);
            start = result.indexOf(PLACEHOLDER_PREFIX, start + replaceContent.length());
        }
        return result.toString();
    }

    /**
     * 解析带有指定占位符的模板字符串，默认占位符为前缀：${  后缀：}<br/><br/>
     * 如：template = category:${}:product:${}<br/>
     * values = {"1", "2"}<br/>
     * 返回 category:1:product:2<br/>
     *
     * @param content 要解析的带有占位符的模板字符串
     * @param values   按照模板占位符索引位置设置对应的值
     * @return
     */
    public String resolve(String content, Object[] values) {
        return resolve(content, Stream.of(values).map(String::valueOf).toArray(String[]::new));
    }

    /**
     * 根据替换规则来替换指定模板中的占位符值
     * @param content  要解析的字符串
     * @param rule  解析规则回调
     * @return
     */
    public String resolveByRule(String content, Function<String, String> rule) {
        int start = content.indexOf(PLACEHOLDER_PREFIX);
        if (start == -1) {
            return content;
        }
        StringBuilder result = new StringBuilder(content);
        while (start != -1) {
            int end = result.indexOf(PLACEHOLDER_SUFFIX, start);
            //获取占位符属性值，如${id}, 即获取id
            String placeholder = result.substring(start + PLACEHOLDER_PREFIX.length(), end);
            //替换整个占位符内容，即将${id}值替换为替换规则回调中的内容
            String replaceContent = placeholder.trim().isEmpty() ? "" : rule.apply(placeholder);
            result.replace(start, end + PLACEHOLDER_SUFFIX.length(), replaceContent);
            start = result.indexOf(PLACEHOLDER_PREFIX, start + replaceContent.length());
        }
        return result.toString();
    }

    /**
     * 替换模板中占位符内容，占位符的内容即为map key对应的值，key为占位符中的内容。<br/><br/>
     * 如：content = product:${id}:detail:${did}<br/>
     *    valueMap = id -> 1; pid -> 2<br/>
     *    经过解析返回 product:1:detail:2<br/>
     *
     * @param content  模板内容。
     * @param valueMap 值映射
     * @return   替换完成后的字符串。
     */
    public String resolveByMap(String content, final Map<String, Object> valueMap) {
        return resolveByRule(content, placeholderValue -> String.valueOf(valueMap.get(placeholderValue)));
    }

    /**
     * 根据properties文件替换占位符内容
     * @param content
     * @param properties
     * @return
     */
    public String resolveByProperties(String content, final Properties properties) {
        return resolveByRule(content, properties::getProperty);
    }

    /**
     * 根据对象中字段路径(即类似js访问对象属性值)替换模板中的占位符 <br/><br/>
     * 如 content = product:${id}:detail:${detail.id} <br/>
     *    obj = Product.builder().id(1).detail(Detail.builder().id(2).build()).build(); <br/>
     *    经过解析返回 product:1:detail:2 <br/>
     *
     * @param content  要解析的内容
     * @param obj   填充解析内容的对象(如果是基本类型，则所有占位符替换为相同的值)
     * @return
     */
    public String resolveByObject(String content, final Object obj) {
        if (obj instanceof Map) {
            return resolveByMap(content, (Map)obj);
        }
        return resolveByRule(content, placeholderValue -> String.valueOf(ReflectionUtils.getValueByFieldPath(obj, placeholderValue)));
    }

    /**
     * 验证字符串内容是否包含下列非法字符<br>
     * `~!#%^&*=+\\|{};:'\",<>/?○●★☆☉♀♂※¤╬の〆
     *
     * @param content
     *  字符串内容
     * @return 't'代表不包含非法字符，otherwise代表包含非法字符。
     */
    private char validateLegalString(String content) {
        String illegal = "`·~!！@$￥%……^&*=（）()+—-\\|{};；:：‘’”“'\",，。？【】、<>/?○●★☆☉♀♂※¤╬の〆";
        char isLegalChar = 't';
        L1: for (int i = 0; i < content.length(); i++) {
            for (int j = 0; j < illegal.length(); j++) {
                if (content.charAt(i) == illegal.charAt(j)) {
                    isLegalChar = content.charAt(i);
                    break L1;
                }
            }
        }
        return isLegalChar;
    }
}
